/*
 * Author:  Copyright (c) 2013 Toby
 * E-Mail:  smurffit@gmail.com
 * Web:     https://code.google.com/p/chrome-mfi-filter4kiva/
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, see <http://www.gnu.org/licenses/>.
 */

'use strict';

/* ----- FUNC ----- */

/**
 * The result of the cleanup() to be returned to background.js
 * @param count
 *            {number} the amount of filtered items
 * @param highlights
 *            {boolean} whether items have been highlighted
 * @param filtered
 *            {Array} the list of filtered MFIs
 */
function result(count, highlights, filtered)
{
	this.count = count;
	this.highlights = highlights;
	this.filtered = filtered;
}

/* ----- END FUNC ----- */

/* ----- VARS ----- */

/**
 * 0 = off, 1 = verbose, 2 = debug
 * @type {number}
 */
var DEBUG = 0;

/**
 * Runtime configuration
 * @see background.config
 * @type {config}
 */
var config = {
//
};

/**
 * filtered result counter, has to be reset on load
 * @type {number}
 */
var count = 0;

/**
 * whether highlighted results have been found, has to be reset on load
 * @type {boolean}
 */
var highlights = false;

var
/**
 * ratings[MFI name] -> {number} rating
 * @type {Object}
 */
ratings,

/**
 * global timer for the DOM event listener
 * @type {window.setTimeout}
 */
timer,

/**
 * global timer to avoid double completions
 * @type {window.setTimeout}
 */
ti,

/**
 * filtered MFIs on the current page so they can be shown on the pop-up page
 * @type {Array}
 */
filtered;

/**
 * HTML to insert if we can't find the rating of a Partner
 * @type {String}
 */
var UPDATE_PARTNERS = '<br /><span style="color:red;">Update Field Partners</span>';
var regExFilter, regExHighlight;
/* ----- END VARS ----- */

/**
 * Drop the previous state to ensure correct results also stops any remaining timers
 */
function resetPage()
{
	if (timer)
	{
		clearTimeout(timer);
		timer = null;
	}

	if (ti)
	{
		clearInterval(ti);
		ti = undefined;
	}

	count = 0;
	highlights = false;
	// TODO if we can rely on a single cleanup() call then filtered can be local
	filtered = [];
}

/**
 * Starts a timer which will call the cleanup() function after the time elapsed. The timer will be
 * reset every time this function is called. The goal is to perform the expensive work as rarely as
 * possible (ideally only once) while keeping the time the page has to wait for us as low as
 * possible.
 */
function scheduleCleanup()
{
	if (config.block || config.highlight)
	{
		if (timer)
		{
			// reset existing timer
			clearTimeout(timer);
		}

		// TODO this may no longer apply with the webRequest API
		// I tried several different timers.
		// Higher timers reduce the overhead but may delay the progress;
		// lower timers can timeout too fast leading to unnecessary cycles
		// 200 should be the maximum, 40 the minimum. 100 Seems to be a good
		// default
		timer = setTimeout(function()
		{
			cleanup();
		}, 80);
	}
}

function initState(request)
{
	ratings = JSON.parse(request.rates);
	config = JSON.parse(request.value);
	regExFilter = new RegExp(config.regExFilter);
	regExHighlight = new RegExp(config.regExHighlight);

	if (getStyleSheet() === undefined)
	{
		initCssStyle();
	}
}

function reset(request)
{

	if (request.type === 'onCompleted')
	{
		setTimeout(function()
		{
			ti = setInterval(function()
			{
				if (document.readyState === 'complete')
				{
					scheduleCleanup();
					clearInterval(ti);
					ti = undefined;
				}
			}, 90);
		}, 140);
	}
	else if (request.type === 'direct')
	{
		setTimeout(function()
		{
			scheduleCleanup();
		}, 140);
	}
	else
	{
		document.getElementById('listingsColStock').addEventListener('DOMNodeInserted',
				scheduleCleanup, false);
	}
}

// Receive messages from the background.js page
chrome.extension.onMessage.addListener(function(request, sender, sendResponse)
{
	// reset is requested when a new page is requested to discard the state
	if (request.action == 'sendRatings')
	{
		initState(request);
		return;
	}
	else if (request.action == 'reset')
	{
		if (ratings === undefined)
		{
			chrome.extension.sendMessage({
				count : 'needRatings'
			});
		}
		else
		{
			config = JSON.parse(request.value);
		}
		if (ti !== undefined)
		{
			clearInterval(ti);
		}
		resetPage();
		if (DEBUG > 1)
		{
			console.log('State: ' + request.value);
		}
		reset(request);
	}
	else if (request.action === 'colorHidden')
	{
		if (DEBUG > 1)
		{
			console.log('colorHidden.');
		}
		sendResponse({
		//
		});
		colorHidden(request.value);
	}
	else if (request.action === 'hiddenShown')
	{
		if (DEBUG > 1)
		{
			console.log('hiddenShown.');
		}
		sendResponse({
			result : (getStyleSheet().cssRules[0].cssText.indexOf(config.colorFilteredHide) == -1)
		});
	}
});

/**
 * Looks for our injected CSS and returns the current state; returns undefined if not found
 * @returns the injected CSS or undefined
 */
function getStyleSheet()
{
	for ( var i = 0, l = document.styleSheets.length; i < l; i++)
	{
		if (document.styleSheets[i].title === 'injected')
		{
			return document.styleSheets[i];
		}
	}
	if (DEBUG > 0)
	{
		console.log('CSS rule not found - creating new one.');
	}
	return undefined;
}

/**
 * Injects our CSS file into the current page
 */
function initCssStyle()
{
	var cssStyle = document.createElement('style');
	cssStyle.type = 'text/css';
	cssStyle.id = 'injected';
	cssStyle.title = 'injected';

	cssStyle.appendChild(document.createTextNode('.filtered{' + config.colorFilteredHide + ';}'));
	cssStyle.appendChild(document.createTextNode('.highlight{' + config.colorHighlighted + ';}'));
	cssStyle.appendChild(document.createTextNode('.g4{margin-right: 4px;}'));
	document.getElementsByTagName('head')[0].appendChild(cssStyle);
}

/**
 * Shows / hides and colorizes filtered items
 * @param show
 *            {boolean} whether the items should be shown or hidden
 */
function colorHidden(show)
{
	if (show)
	{
		setCssRule(0, '.filtered{' + config.colorFilteredShow + ';}');
	}
	else
	{
		setCssRule(0, '.filtered{' + config.colorFilteredHide + ';}');
		// showing and hiding several items messes up the scroll position
		// (usually ends up with the page scrolled down to the bottom). Avoid this by scrolling to
		// the top of the list.
		document.getElementById('listingsForm').scrollIntoView();
	}
}

/**
 * Inserts or replaces a CSS rule with the given content
 * @param ruleId
 *            {number} the ruleId
 * @param ruleContent
 *            {String} the CCSS content of the rule
 */
function setCssRule(ruleId, ruleContent)
{
	var cssStyle = getStyleSheet();

	// XXX remove once we're certain
	if (cssStyle === undefined)
	{
		console.log('CSS rule not found - shouldn\'t happen at this point!');
		initCssStyle();
		cssStyle = getStyleSheet();
	}

	if (ruleId < cssStyle.rules.length)
	{
		cssStyle.deleteRule(ruleId);
	}
	cssStyle.insertRule(ruleContent, ruleId);
}

/**
 * Checks for RegExp issues like an open loan page which is uninteresting a match to /lend*
 * @param bFilterValid
 *            {boolean} whether the filter RegExp is valid
 * @param bHighlightValid
 *            {boolean} whether the highlight RegExp is valid
 * @returns {boolean} whether the pre-check was successful
 */
function precheckCleanup(bFilterValid, bHighlightValid)
{
	if (DEBUG > 0)
	{
		console.log('precheckCleanup()');
	}

	if (!config.block && !config.highlight)
	{
		console.log('Invalid filter configured.');
		chrome.extension.sendMessage(new result('E1', false, ''));
		return false;
	}
	else if (!bFilterValid && !bHighlightValid)
	{
		console.log('Invalid filter configured: ' + bFilterValid + ' / ' + bHighlightValid);
		chrome.extension.sendMessage(new result('E1', false, ''));
		return false;
	}
	else if (!document.getElementById('listingsColStock'))
	{
		console.log('Couldn\'t find result coloumn.');
		chrome.extension.sendMessage(new result('E2', false, ''));
		return false;
	}
	return true;
}

// The core method of this extension goes through the results and hides unwanted
// entries
function cleanup()
{
	// don't use empty RegExp, otherwise everything gets filtered
	var bFilterValid = config.block && regExFilter && (regExFilter.source.length > 6);
	var bHighlightValid = config.highlight && regExHighlight && (regExHighlight.source.length > 6);

	var listingsColStock = document.getElementById('listingsColStock');
	var itemNode, articleNodes = listingsColStock.getElementsByTagName('article');

	for ( var i = 0, l = articleNodes.length; i < l; i++)
	{
		// usually node 7 is correct, for teams it's node 9
		itemNode = articleNodes[i].childNodes[3].childNodes[1].childNodes[7];
		if (itemNode.getAttribute('class') !== 'mfi')
		{
			itemNode = articleNodes[i].childNodes[3].childNodes[1].childNodes[9];
			if (itemNode.getAttribute('class') !== 'mfi')
			{
				console.log('not mfi: ' +
						articleNodes[i].childNodes[3].childNodes[1].childNodes[7].innerHTML);
				continue;
			}
		}

		// node is not filtered, check if it should be
		// don't RegExp as of http://jsperf.com/regex-or-indexof2
		if ((articleNodes[i].className.indexOf('filtered') === -1) &&
				(articleNodes[i].className.indexOf('highlight') === -1))
		{
			if (bFilterValid && regExFilter.test(itemNode.textContent))
			{
				count++;
				articleNodes[i].className += ' filtered';
				filtered.push(itemNode.textContent.substring(12));
			}
			else if (bHighlightValid && regExHighlight.test(itemNode.textContent))
			{
				highlights = true;
				articleNodes[i].className += ' highlight';
			}
		}
		injectStars(itemNode);
	}

	if (!precheckCleanup(bFilterValid, bHighlightValid))
	{
		if (DEBUG > 0)
		{
			console.log('Precheck failed.');
		}
		return;
	}

	if (count > 0)
	{
		var sLoans = document.getElementById('resultsNumber');
		var iLoans = parseInt(sLoans.textContent.replace(/\D/g, ''), 10);
		if (iLoans !== Number.NaN)
		{
			sLoans.innerHTML = (iLoans - count) + '<sup title="' + iLoans + '">+' + count +
					'</sup>';
		}
		else
		{
			console.log('sLoans undefined: ' + sLoans);
		}
		if (articleNodes.length === count)
		{
			showAllFiltered();
		}
	}

	// let the background.js know what we found
	listingsColStock.removeEventListener('DOMNodeInserted', scheduleCleanup);
	chrome.extension.sendMessage(new result(count, highlights, filtered.join('#')));
	setCssRule(0, '.filtered{' + config.colorFilteredHide + ';}');

	if (DEBUG > 0)
	{
		console.log('Filtered: ' + count + ' / Highlights: ' + highlights);
	}
}

function injectStars(itemNode)
{
	if (config.showRatingStars && (itemNode.innerHTML.indexOf('rating') === -1))
	{
		var stars = ratings[itemNode.textContent.substring(12)];

		if (stars === undefined && (itemNode.innerHTML.indexOf('Update') === -1))
		{
			if (DEBUG > 0)
			{
				console.log(itemNode.textContent.substring(12));
			}
			itemNode.innerHTML += UPDATE_PARTNERS;
		}
		else if (stars !== Number.NaN)
		{
			itemNode.innerHTML += '<br /><br /><span class="rating-static rating-' + stars +
					'"></span>';
		}
	}
	if (DEBUG > 0 && (itemNode.innerHTML.indexOf('undefined') > 0))
	{
		console.log('undefined: ' + itemNode.innerHTML);
	}
}
function showAllFiltered()
{
	var listingsColStock = document.getElementById('listingsColStock');
	if (listingsColStock.innerHTML.indexOf('were filtered') > 0)
	{
		console.log('Message already present.');
		return;
	}
	var ul = listingsColStock.getElementsByTagName('ul')[1];
	var li = document.createElement('li');
	var h2 = document.createElement('h2');

	li.className = 'lendHeader_new lendHeader_new2';
	h2.textContent = 'Sorry, all loans on this page were filtered.';
	li.appendChild(h2);
	ul.appendChild(li);
}
